package p2p.peer;

import java.util.HashSet;
import java.util.Set;
import p2p.logic.connection.ConnectionOwner;
import p2p.logic.connection.client.Client;
import p2p.logic.connection.communication.AbstractCommunicationHandler;
import p2p.logic.connection.communication.message.Message;
import p2p.logic.connection.communication.message.MessageSplitter;
import p2p.logic.log.Logger;
/** 
 * CommunicationHandler is responsible handling incoming
 * and outgoing messages. 
 * For incoming Messages it saved the UUID of the message 
 * to know which message has to be ackknowlegded
 */
public class CommunicationHandler extends AbstractCommunicationHandler{
	
	public CommunicationHandler(ConnectionOwner owner) {
		super(owner);
		
	}

	public synchronized void handleReceived(byte[] recieved) {
		
		Message message = new Message(recieved);
		
		this.processReceivedMessage(message);
		
	}
	
	public synchronized void handleReceivedMessage(Message message) {
				
		this.processReceivedMessage(message);
		
	}
	
	public synchronized void handleSended(byte[] send){
		
		Message message = new Message(send);
		
		this.processSendMessage(message);
	
	}
	
	public synchronized void handleSendedMessage(Message send){
				
		this.processSendMessage(send);
	
	}

	public synchronized void processReceivedMessage(Message message) {
		
		Set<Message> messageSet = this.messageReceivedMap.get(message.getUuid());

		if (messageSet == null)
			messageSet = new HashSet<Message>();

		this.messageReceivedMap.put(message.getUuid(), messageSet);
		
		if (message.isAck()){
						
			if (isReceivedMessageComplete(message)){
				Logger.debug(this.owner.getId() + "  ack " + message.getUuid().toString());

				Set<Message> messages = this.messageSendMap.get(message.getUuid());
				
				if (messages != null)
					this.owner.recievedAck(new MessageSplitter().assembleMessage(messages));

				this.messageReceivedMap.remove(message.getUuid());
		
			}
			
		}else{
			
			messageSet.add(message);

			if (message.isBroadCast()){

				this.owner.recievedMessage(message);

				this.broadCastMessages.add(message.getUuid());
				
				return ;
			}
			
			if (message.getTotalNumber() == message.getSequenceNumber() && message.getTotalNumber()  == 1){
							
				this.owner.getGateway().send(new Client(message.getSenderIp(),message.getSenderPort()),message.getUuid());
				
				this.owner.recievedMessage(message);

			}
			

		}
		
	}
	
	public synchronized void processSendMessage(Message message) {

		Set<Message> messageSet = this.messageSendMap.get(message.getUuid());

		if (messageSet == null)
			messageSet = new HashSet<Message>();

		messageSet.add(message);
		
		this.messageSendMap.put(message.getUuid(), messageSet);
		
		if (isSendMessageComplete(message)){
			
			this.ackMessages.add(message.getUuid());

		}
		
		if (message.isBroadCast())
			this.broadCastMessages.add(message.getUuid());
			
		if (!message.isAck() && !message.isBroadCast())
			this.owner.getGateway().waitForAck(message.getUuid());
		
	}

	private boolean isReceivedMessageComplete(Message message) {
		Set<Message> messageSet = this.messageReceivedMap.get(message.getUuid());
		
		Set<Integer> sequnceNumberSet = new HashSet<Integer>();
		
		if (messageSet.size() == 0)
			return true;
		
		for (Message m : messageSet)
			sequnceNumberSet.add(m.getSequenceNumber());

		if (sequnceNumberSet.size() == message.getTotalNumber())
			return true;

		return false;
	}

	private boolean isSendMessageComplete(Message message) {

		Set<Message> messageSet = this.messageSendMap.get(message.getUuid());
		Set<Integer> sequnceNumberSet = new HashSet<Integer>();

		for (Message m : messageSet)
			sequnceNumberSet.add(m.getSequenceNumber());

		if (sequnceNumberSet.size() == message.getTotalNumber())
			return true;

		return false;
	}

	@Override
	public String toString(){
		return this.owner.getId();
	}
}
